/*
     Copyright 2007 Joseph M. Ferner

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using log4net;
using OpenServers.Net.Common.Socket;
using OpenServers.Net.FtpServer.State;

namespace OpenServers.Net.FtpServer.Command {
    /// <summary>
    /// command which implements the basic logic for sending data to the client.
    /// </summary>
    public abstract class BaseSendDataCommand : BaseCommand {
        private static readonly ILog log = LogManager.GetLogger(typeof(BaseSendDataCommand));

        /// <summary>
        /// Send data to the client.
        /// </summary>
        protected virtual void SendData(IFtpServer server, FtpState state, Stream dataStream) {
            if (state.IsPassive) {
                throw new NotImplementedException("Passive is not implemented");
            } else {
                ISocket socket = state.Listener.CreateSocket();
                //socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, 1);
                socket.Bind(state.ClientSocket.LocalAddress, 20);
                SendDataState sendDataState = new SendDataState(server, state, socket, dataStream);
                sendDataState.Socket.BeginConnect(state.LastPortCommandIpAddress, state.LastPortCommandPort.Value, ConnectCallback, sendDataState);
            }
        }

        /// <summary>
        /// Called when the connect is successful.
        /// </summary>
        protected virtual void ConnectCallback(IAsyncResult ar) {
            SendDataState state = (SendDataState)ar.AsyncState;
            try {
                state.Socket.EndConnect(ar);
                SendMoreData(state);
            } catch (Exception ex) {
                log.Error("", ex);
                state.State.FireError(ex);
            }
        }

        /// <summary>
        /// Called when the send data is successul.
        /// </summary>
        protected virtual void SendCallback(IAsyncResult ar) {
            SendDataState state = (SendDataState)ar.AsyncState;
            try {
                SocketError errorCode;
                state.Socket.EndSend(ar, out errorCode);
                SendMoreData(state);
            } catch (Exception ex) {
                log.Error("", ex);
                state.State.FireError(ex);
            }
        }

        /// <summary>
        /// Called to send more data to the client.
        /// </summary>
        protected virtual void SendMoreData(SendDataState state) {
            int count = Math.Min((int)(state.FileInputStream.Length - state.FileInputStream.Position), 10 * 1024);
            if (count == 0) {
                state.Socket.BeginDisconnect(false, DisconnectCallback, state);
            } else {
                byte[] buffer = new byte[count];
                count = state.FileInputStream.Read(buffer, 0, count);
                state.Socket.BeginSend(buffer, 0, count, SocketFlags.None, SendCallback, state);
            }
        }

        /// <summary>
        /// called when the connection is closed.
        /// </summary>
        protected virtual void DisconnectCallback(IAsyncResult ar) {
            SendDataState state = (SendDataState)ar.AsyncState;
            try {
                state.Socket.EndDisconnect(ar);
                state.State.Listener.Send(state.State, state.Server.Messages.Get226());
                log.DebugFormat("done sending data");
            } catch (Exception ex) {
                log.Error("", ex);
                state.State.FireError(ex);
            } finally {
                state.Socket.Close();
            }
        }

        /// <summary>
        /// state object to manage the state as data is sent to the client.
        /// </summary>
        protected class SendDataState {
            private readonly FtpState _state;
            private readonly ISocket _socket;
            private readonly Stream _fileInputStream;
            private readonly IFtpServer _server;

            /// <summary>
            /// constructor.
            /// </summary>
            public SendDataState(IFtpServer server, FtpState state, ISocket socket, Stream fileInputStream) {
                this._server = server;
                this._state = state;
                this._socket = socket;
                this._fileInputStream = fileInputStream;
            }

            /// <summary>
            /// The FtpState object of the connection.
            /// </summary>
            public FtpState State {
                get { return _state; }
            }

            /// <summary>
            /// The send data socket.
            /// </summary>
            public ISocket Socket {
                get { return _socket; }
            }

            /// <summary>
            /// The input stream to read from to send to the client.
            /// </summary>
            public Stream FileInputStream {
                get { return _fileInputStream; }
            }

            /// <summary>
            /// The server.
            /// </summary>
            public IFtpServer Server {
                get { return _server; }
            }
        }
    }
}
